-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat: Extend Claude Code support for alternative providers (Z.ai, Qwen, DeepSeek) #8453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…n, DeepSeek) - Add configuration file reading from multiple locations (~/.claude/settings.json, etc.) - Implement provider detection based on ANTHROPIC_BASE_URL environment variable - Support dynamic model selection for Z.ai, Qwen (Alibaba Cloud), and DeepSeek providers - Pass environment variables from Claude Code config to subprocess - Update frontend to display alternative provider models dynamically - Add comprehensive test coverage for new functionality - Update existing tests to handle async initialization Fixes #8452
| } | ||
|
|
||
| // Check for Qwen (Alibaba Cloud/Dashscope) | ||
| if (baseUrl.includes("dashscope.aliyuncs.com") || baseUrl.includes("aliyuncs.com")) { |
Check failure
Code scanning / CodeQL
Incomplete URL substring sanitization High
dashscope.aliyuncs.com
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 2 months ago
To fix this, we should:
- Properly parse the given
baseUrlas a URL object. - Extract the
host(and optionally thehostname) using standard parsing, for example withnew URL(baseUrl).hostornew URL(baseUrl).hostname. - Perform equality checks or use explicit allowed suffix matches (
.endsWith()) on the parsed host, rather than unchecked substring search on the whole URL.
This change should be done inside the detectProviderFromConfig method.
Parsing might throw if the URL is malformed, so ideally catch and default to null.
Any includes checks for hosts must be replaced with precise suffix or equality checks on the parsed host/hostname.
No new dependencies are needed, as the Node.js runtime URL class suffices.
-
Copy modified lines R192-R198 -
Copy modified line R200 -
Copy modified lines R206-R207 -
Copy modified lines R212-R213
| @@ -189,19 +189,28 @@ | ||
| return null | ||
| } | ||
|
|
||
| let host: string | ||
| try { | ||
| host = new URL(baseUrl).hostname | ||
| } catch { | ||
| return null | ||
| } | ||
|
|
||
| // Check for Z.ai | ||
| if (baseUrl.includes("z.ai")) { | ||
| if (host === "z.ai" || host.endsWith(".z.ai")) { | ||
| // Return all Z.ai models | ||
| return { provider: "zai", models: internationalZAiModels } | ||
| } | ||
|
|
||
| // Check for Qwen (Alibaba Cloud/Dashscope) | ||
| if (baseUrl.includes("dashscope.aliyuncs.com") || baseUrl.includes("aliyuncs.com")) { | ||
| if (host === "dashscope.aliyuncs.com" || host.endsWith(".dashscope.aliyuncs.com") || | ||
| host === "aliyuncs.com" || host.endsWith(".aliyuncs.com")) { | ||
| return { provider: "qwen-code", models: qwenCodeModels } | ||
| } | ||
|
|
||
| // Check for DeepSeek | ||
| if (baseUrl.includes("deepseek.com") || baseUrl.includes("api.deepseek.com")) { | ||
| if (host === "deepseek.com" || host.endsWith(".deepseek.com") || | ||
| host === "api.deepseek.com" || host.endsWith(".api.deepseek.com")) { | ||
| return { provider: "deepseek", models: deepSeekModels } | ||
| } | ||
|
|
| } | ||
|
|
||
| // Check for Qwen (Alibaba Cloud/Dashscope) | ||
| if (baseUrl.includes("dashscope.aliyuncs.com") || baseUrl.includes("aliyuncs.com")) { |
Check failure
Code scanning / CodeQL
Incomplete URL substring sanitization High
aliyuncs.com
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 2 months ago
How to fix:
Rather than checking if "aliyuncs.com" is a substring anywhere in baseUrl, the code should first parse baseUrl and then check the host component exactly against a whitelist of valid provider domains. This ensures that accidental or malicious inclusion of substrings in other URL fields or superdomains doesn't affect provider detection.
Implementation details:
- Parse
baseUrlto extract the hostname. - Check if hostname is exactly one of a whitelist for Qwen provider (e.g.,
dashscope.aliyuncs.com,aliyuncs.com), or is a subdomain of these. - This requires using Node's built-in
URLclass (in all supported Node versions). - Modify lines 186–206 to perform host-based checks instead of substring checks.
- Add a helper function for subdomain matching.
- No external dependencies are needed.
-
Copy modified lines R192-R198 -
Copy modified line R200 -
Copy modified lines R206-R211 -
Copy modified lines R216-R221
| @@ -189,19 +189,36 @@ | ||
| return null | ||
| } | ||
|
|
||
| let hostname: string | undefined | ||
| try { | ||
| hostname = new URL(baseUrl).hostname | ||
| } catch { | ||
| return null | ||
| } | ||
|
|
||
| // Check for Z.ai | ||
| if (baseUrl.includes("z.ai")) { | ||
| if (hostname === "z.ai" || hostname.endsWith(".z.ai")) { | ||
| // Return all Z.ai models | ||
| return { provider: "zai", models: internationalZAiModels } | ||
| } | ||
|
|
||
| // Check for Qwen (Alibaba Cloud/Dashscope) | ||
| if (baseUrl.includes("dashscope.aliyuncs.com") || baseUrl.includes("aliyuncs.com")) { | ||
| if ( | ||
| hostname === "dashscope.aliyuncs.com" || | ||
| hostname === "aliyuncs.com" || | ||
| hostname.endsWith(".dashscope.aliyuncs.com") || | ||
| hostname.endsWith(".aliyuncs.com") | ||
| ) { | ||
| return { provider: "qwen-code", models: qwenCodeModels } | ||
| } | ||
|
|
||
| // Check for DeepSeek | ||
| if (baseUrl.includes("deepseek.com") || baseUrl.includes("api.deepseek.com")) { | ||
| if ( | ||
| hostname === "deepseek.com" || | ||
| hostname === "api.deepseek.com" || | ||
| hostname.endsWith(".deepseek.com") || | ||
| hostname.endsWith(".api.deepseek.com") | ||
| ) { | ||
| return { provider: "deepseek", models: deepSeekModels } | ||
| } | ||
|
|
| } | ||
|
|
||
| // Check for DeepSeek | ||
| if (baseUrl.includes("deepseek.com") || baseUrl.includes("api.deepseek.com")) { |
Check failure
Code scanning / CodeQL
Incomplete URL substring sanitization High
deepseek.com
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 2 months ago
To resolve the incomplete substring URL sanitization error, we should parse the baseUrl using the built-in URL constructor and extract the hostname. Then, perform the provider matching by comparing the hostname (and/or subdomain) rather than looking for substrings anywhere in baseUrl. For "deepseek.com", we should match deepseek.com and its direct subdomains (i.e., api.deepseek.com). We similarly update the other substring matches within the function for consistency. Only the function detectProviderFromConfig (lines 179–209) needs to change. We will need to handle parsing errors gracefully.
No new dependencies are needed, as the global URL class is available in Node and browsers.
-
Copy modified lines R192-R202 -
Copy modified lines R208-R213 -
Copy modified lines R218-R221
| @@ -189,19 +189,36 @@ | ||
| return null | ||
| } | ||
|
|
||
| // Check for Z.ai | ||
| if (baseUrl.includes("z.ai")) { | ||
| // Parse baseUrl to extract hostname | ||
| let hostname: string | undefined; | ||
| try { | ||
| hostname = new URL(baseUrl).hostname; | ||
| } catch (_) { | ||
| // Invalid URL, fallback to substring checks for compatibility, or return null | ||
| return null | ||
| } | ||
|
|
||
| // Check for Z.ai (z.ai or subdomain) | ||
| if (hostname === "z.ai" || hostname.endsWith(".z.ai")) { | ||
| // Return all Z.ai models | ||
| return { provider: "zai", models: internationalZAiModels } | ||
| } | ||
|
|
||
| // Check for Qwen (Alibaba Cloud/Dashscope) | ||
| if (baseUrl.includes("dashscope.aliyuncs.com") || baseUrl.includes("aliyuncs.com")) { | ||
| if ( | ||
| hostname === "dashscope.aliyuncs.com" || | ||
| hostname.endsWith(".dashscope.aliyuncs.com") || | ||
| hostname === "aliyuncs.com" || | ||
| hostname.endsWith(".aliyuncs.com") | ||
| ) { | ||
| return { provider: "qwen-code", models: qwenCodeModels } | ||
| } | ||
|
|
||
| // Check for DeepSeek | ||
| if (baseUrl.includes("deepseek.com") || baseUrl.includes("api.deepseek.com")) { | ||
| if ( | ||
| hostname === "deepseek.com" || | ||
| hostname.endsWith(".deepseek.com") | ||
| ) { | ||
| return { provider: "deepseek", models: deepSeekModels } | ||
| } | ||
|
|
| } | ||
|
|
||
| // Check for DeepSeek | ||
| if (baseUrl.includes("deepseek.com") || baseUrl.includes("api.deepseek.com")) { |
Check failure
Code scanning / CodeQL
Incomplete URL substring sanitization High
api.deepseek.com
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 2 months ago
The best fix is to parse the baseUrl using the standard URL parser (the global URL class in Node.js/TypeScript). Then, verify the host portion directly rather than using a substring search. For all provider checks, replace the current substring matching with explicit comparisons to whitelisted host values (or their exact subdomains if intended). This preserves the intent ("identify if this is DeepSeek, Z.ai, Qwen, etc.") without risk of false positives due to substring matching elsewhere in the URL. Edits needed: in detectProviderFromConfig, replace substring matching checks on baseUrl with checks on host, using the parsed URL. Add the required error handling for malformed URLs.
-
Copy modified lines R192-R199 -
Copy modified lines R201-R202 -
Copy modified lines R207-R212 -
Copy modified lines R217-R221
| @@ -189,19 +189,36 @@ | ||
| return null | ||
| } | ||
|
|
||
| let host = ""; | ||
| try { | ||
| host = new URL(baseUrl).host | ||
| } catch (err) { | ||
| // Invalid URL | ||
| return null | ||
| } | ||
|
|
||
| // Check for Z.ai | ||
| if (baseUrl.includes("z.ai")) { | ||
| // Return all Z.ai models | ||
| // Accept z.ai and any subdomain thereof | ||
| if (host === "z.ai" || host.endsWith(".z.ai")) { | ||
| return { provider: "zai", models: internationalZAiModels } | ||
| } | ||
|
|
||
| // Check for Qwen (Alibaba Cloud/Dashscope) | ||
| if (baseUrl.includes("dashscope.aliyuncs.com") || baseUrl.includes("aliyuncs.com")) { | ||
| if ( | ||
| host === "dashscope.aliyuncs.com" || | ||
| host.endsWith(".dashscope.aliyuncs.com") || | ||
| host === "aliyuncs.com" || | ||
| host.endsWith(".aliyuncs.com") | ||
| ) { | ||
| return { provider: "qwen-code", models: qwenCodeModels } | ||
| } | ||
|
|
||
| // Check for DeepSeek | ||
| if (baseUrl.includes("deepseek.com") || baseUrl.includes("api.deepseek.com")) { | ||
| if ( | ||
| host === "api.deepseek.com" || | ||
| host === "deepseek.com" || | ||
| host.endsWith(".deepseek.com") | ||
| ) { | ||
| return { provider: "deepseek", models: deepSeekModels } | ||
| } | ||
|
|
| const isAlternativeProvider = | ||
| baseUrl && | ||
| (baseUrl.includes("z.ai") || | ||
| baseUrl.includes("dashscope.aliyuncs.com") || |
Check failure
Code scanning / CodeQL
Incomplete URL substring sanitization High
dashscope.aliyuncs.com
| baseUrl && | ||
| (baseUrl.includes("z.ai") || | ||
| baseUrl.includes("dashscope.aliyuncs.com") || | ||
| baseUrl.includes("aliyuncs.com") || |
Check failure
Code scanning / CodeQL
Incomplete URL substring sanitization High
aliyuncs.com
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 2 months ago
To fix this issue, the code must properly parse the baseUrl as a URL and then check its hostname (not the entire URL string) against an explicit list of allowed host patterns or known hosts. This should only compare the host portion, not any part of the path, query, or protocol, thus avoiding the incomplete match vulnerability.
Specifically, in file src/api/providers/claude-code.ts, replace the substring .includes(...) approach in the definition of isAlternativeProvider (lines 224-231) with a check that:
- Uses the
URLconstructor to parsebaseUrland retrieve itshostname. - Checks if the parsed
hostnameis either exactly one of the allowed hostnames or matches a list of known patterns. (If support for subdomains is needed—for example, anything ending with.aliyuncs.com—we can useendsWithon the hostname.)
No external libraries are needed for this fix as Node.js's standard URL object provides sufficient functionality.
-
Copy modified lines R226-R249
| @@ -223,11 +223,30 @@ | ||
| // Detect if we're using an alternative provider | ||
| const isAlternativeProvider = | ||
| baseUrl && | ||
| (baseUrl.includes("z.ai") || | ||
| baseUrl.includes("dashscope.aliyuncs.com") || | ||
| baseUrl.includes("aliyuncs.com") || | ||
| baseUrl.includes("deepseek.com") || | ||
| baseUrl.includes("api.deepseek.com")) | ||
| (() => { | ||
| try { | ||
| const hostname = new URL(baseUrl).hostname; | ||
| const allowedAlternativeHostSuffixes = [ | ||
| "z.ai", | ||
| "dashscope.aliyuncs.com", | ||
| "aliyuncs.com", | ||
| "deepseek.com", | ||
| ]; | ||
| const allowedExactAlternativeHosts = [ | ||
| "api.deepseek.com" | ||
| ]; | ||
| // Check for exact match or suffix match (subdomain support) | ||
| if ( | ||
| allowedExactAlternativeHosts.includes(hostname) || | ||
| allowedAlternativeHostSuffixes.some(suffix => hostname === suffix || hostname.endsWith(`.${suffix}`)) | ||
| ) { | ||
| return true; | ||
| } | ||
| return false; | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| })() | ||
|
|
||
| let finalModelId: string = model.id | ||
| if (isAlternativeProvider) { |
| (baseUrl.includes("z.ai") || | ||
| baseUrl.includes("dashscope.aliyuncs.com") || | ||
| baseUrl.includes("aliyuncs.com") || | ||
| baseUrl.includes("deepseek.com") || |
Check failure
Code scanning / CodeQL
Incomplete URL substring sanitization High
deepseek.com
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 2 months ago
General fix:
Instead of checking if baseUrl includes certain substrings, parse the baseUrl as a URL, extract the hostname, and compare the hostname against an explicit whitelist of trusted hosts (and/or recognized subdomains). Do not use substring comparisons on the whole URL.
Detailed fix:
- In
src/api/providers/claude-code.ts, add an import for Node's built-inurlmodule (import { URL } from "url"). - Define an array containing the allowed hostnames and subdomains for the alternative providers (e.g.
"z.ai","dashscope.aliyuncs.com","aliyuncs.com","deepseek.com","api.deepseek.com"). - When evaluating
isAlternativeProvider, ifbaseUrlis present:- Parse it with
new URL(baseUrl), and extract thehostname. - Check whether the host matches any of the allowed hosts, or (optionally) whether it ends with a dot plus the allowed host (to allow subdomains).
- Parse it with
- Update the logic for
isAlternativeProvideraccordingly, replacing theincludeschecks with proper host checks.
Additional notes:
- This only requires importing
URLfrom"url"and modifying the relevant block. - Existing functionality will not change—only the safety of the provider selection logic will be improved.
-
Copy modified line R22 -
Copy modified lines R224-R244
| @@ -19,7 +19,7 @@ | ||
| import * as os from "os" | ||
| import * as path from "path" | ||
| import { promises as fs } from "fs" | ||
|
|
||
| import { URL } from "url" | ||
| export class ClaudeCodeHandler extends BaseProvider implements ApiHandler { | ||
| private options: ApiHandlerOptions | ||
| private cachedConfig: any = null | ||
| @@ -221,13 +221,27 @@ | ||
| const baseUrl = config?.env?.ANTHROPIC_BASE_URL | ||
|
|
||
| // Detect if we're using an alternative provider | ||
| const isAlternativeProvider = | ||
| baseUrl && | ||
| (baseUrl.includes("z.ai") || | ||
| baseUrl.includes("dashscope.aliyuncs.com") || | ||
| baseUrl.includes("aliyuncs.com") || | ||
| baseUrl.includes("deepseek.com") || | ||
| baseUrl.includes("api.deepseek.com")) | ||
| const allowedAlternativeHosts = [ | ||
| "z.ai", | ||
| "dashscope.aliyuncs.com", | ||
| "aliyuncs.com", | ||
| "deepseek.com", | ||
| "api.deepseek.com" | ||
| ] | ||
| const isAlternativeProvider = (() => { | ||
| if (!baseUrl) return false | ||
| try { | ||
| const { hostname } = new URL(baseUrl) | ||
| // Check direct host match OR subdomain | ||
| return allowedAlternativeHosts.some( | ||
| allowed => | ||
| hostname === allowed || | ||
| hostname.endsWith("." + allowed) | ||
| ) | ||
| } catch (e) { | ||
| return false | ||
| } | ||
| })() | ||
|
|
||
| let finalModelId: string = model.id | ||
| if (isAlternativeProvider) { |
| baseUrl.includes("dashscope.aliyuncs.com") || | ||
| baseUrl.includes("aliyuncs.com") || | ||
| baseUrl.includes("deepseek.com") || | ||
| baseUrl.includes("api.deepseek.com")) |
Check failure
Code scanning / CodeQL
Incomplete URL substring sanitization High
api.deepseek.com
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 2 months ago
General Fix:
Refactor the logic to properly parse the baseUrl using the WhatWG URL class, then extract and compare only the hostname portion against a whitelist of known/allowed hosts (including subdomains, if necessary). The code should no longer use includes on the full URL string.
Detailed Fix:
- At the site where
isAlternativeProvideris set, instead of matching substrings anywhere, extract the actual hostname (e.g.,api.deepseek.com) and compare it (via.endsWithor a whitelistincludes). - Prepare a whitelist/set of allowed hostnames; recommendations from the code are:
z.ai,dashscope.aliyuncs.com,aliyuncs.com,deepseek.com,api.deepseek.com.- If you intend to allow all subdomains for some root (e.g.,
*.z.ai), an.endsWithon hostname (with extra logic to block things likemaliciousz.ai) is needed.
- If you intend to allow all subdomains for some root (e.g.,
- Add import of Node's global
URLclass if not available in the snippet's context.
File, Line:
In src/api/providers/claude-code.ts, refactor the isAlternativeProvider assignment, replacing lines 224–230, and import any needed definitions at the top.
-
Copy modified lines R224-R241
| @@ -221,13 +221,24 @@ | ||
| const baseUrl = config?.env?.ANTHROPIC_BASE_URL | ||
|
|
||
| // Detect if we're using an alternative provider | ||
| const isAlternativeProvider = | ||
| baseUrl && | ||
| (baseUrl.includes("z.ai") || | ||
| baseUrl.includes("dashscope.aliyuncs.com") || | ||
| baseUrl.includes("aliyuncs.com") || | ||
| baseUrl.includes("deepseek.com") || | ||
| baseUrl.includes("api.deepseek.com")) | ||
| const alternativeProviderHostnames = [ | ||
| "z.ai", // allow any subdomain of z.ai | ||
| "dashscope.aliyuncs.com", // allow any subdomain if needed | ||
| "aliyuncs.com", // allow any subdomain if needed | ||
| "deepseek.com", // allow any subdomain if needed | ||
| "api.deepseek.com" | ||
| ] | ||
| let isAlternativeProvider = false | ||
| if (baseUrl) { | ||
| try { | ||
| const urlHost = new URL(baseUrl).hostname | ||
| isAlternativeProvider = alternativeProviderHostnames.some(allowedHost => | ||
| urlHost === allowedHost || urlHost.endsWith("." + allowedHost) | ||
| ) | ||
| } catch (e) { | ||
| isAlternativeProvider = false | ||
| } | ||
| } | ||
|
|
||
| let finalModelId: string = model.id | ||
| if (isAlternativeProvider) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initiating self-review: evaluating my own code like a mirror debugging a mirror—existential stack overflow imminent.
|
|
||
| const selectedProviderModels = useMemo(() => { | ||
| // Check for dynamic models from claude-code alternative providers | ||
| // Use type assertion since claude-code is not part of RouterModels type but we add it dynamically |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Dynamic Claude Code models may not load unless router models are explicitly requested. The debounced refresh posts requestRouterModels for 'litellm' and 'deepinfra' but not for 'claude-code'. If the hook doesn't auto-fetch on mount, users selecting Claude Code won't see alternative provider models. Consider including 'claude-code' in the condition.
| // Use type assertion since claude-code is not part of RouterModels type but we add it dynamically | |
| } else if (selectedProvider === "litellm" || selectedProvider === "deepinfra" || selectedProvider === "claude-code") { | |
| vscode.postMessage({ type: "requestRouterModels" }) | |
| } |
| try { | ||
| const providerInfo = await this.detectProviderFromConfig() | ||
| if (providerInfo) { | ||
| const { provider, models } = providerInfo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Unused variable. 'provider' from providerInfo is not used and will trigger lint warnings; remove it to keep the code clean.
| const { provider, models } = providerInfo | |
| const { models } = providerInfo |
|
|
||
| export async function* runClaudeCode( | ||
| options: ClaudeCodeOptions & { maxOutputTokens?: number }, | ||
| options: ClaudeCodeOptions & { maxOutputTokens?: number; envVars?: Record<string, string> }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P3] Type duplication. 'envVars' is already part of ClaudeCodeOptions; re-declaring it in the intersection type is redundant and can confuse tooling.
| options: ClaudeCodeOptions & { maxOutputTokens?: number; envVars?: Record<string, string> }, | |
| export async function* runClaudeCode( | |
| options: ClaudeCodeOptions & { maxOutputTokens?: number }, | |
| ): AsyncGenerator<ClaudeCodeMessage | string> { |
| maxOutputTokens, | ||
| }: ClaudeCodeOptions & { maxOutputTokens?: number }) { | ||
| envVars, | ||
| }: ClaudeCodeOptions & { maxOutputTokens?: number; envVars?: Record<string, string> }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P3] Same duplication in the helper signature: remove the repeated 'envVars' in the intersection type since it's already part of ClaudeCodeOptions.
| }: ClaudeCodeOptions & { maxOutputTokens?: number; envVars?: Record<string, string> }) { | |
| }: ClaudeCodeOptions & { maxOutputTokens?: number }) { |
| */ | ||
| static async getAvailableModels( | ||
| claudeCodePath?: string, | ||
| ): Promise<{ provider: string; models: Record<string, ModelInfo> } | null> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P3] Return type states Promise<... | null> but the implementation always returns an object (defaults to 'claude-code' on errors). Consider removing '| null' for accuracy.
| ): Promise<{ provider: string; models: Record<string, ModelInfo> } | null> { | |
| ): Promise<{ provider: string; models: Record<string, ModelInfo> }> { |
|
@roomote fix codeql errors |
|
Acknowledged. I will pull CodeQL scan results for feat/claude-code-alternative-providers, implement fixes, and push updates shortly. |
…rigger CodeQL re-run
Review SummaryI've completed a re-review of PR #8453 after the latest security improvements. The new changes add proper URL scheme validation and security attributes to external links, which are good security practices. Issues FoundThe following issues from the previous review remain unaddressed:
Latest Changes (5a3a101)Security Improvements - No new issues found:
Review StatusCode Quality: The latest security changes are well-implemented. The P1 issue regarding dynamic model loading for claude-code alternative providers should be addressed before merging. Recommendation: Address the P1 issue to ensure the feature works correctly for all users. The P2 and P3 issues are minor cleanup items that can be addressed at your discretion. |
… add rel="noopener noreferrer" on external links
|
CodeQL hardening applied and tests green. Summary of fixes:
Validation:
CI/CodeQL:
|
| } else { | ||
| console.warn(`Blocked external URL with disallowed scheme: ${url}`) | ||
| vscode.window.showErrorMessage( | ||
| t("common:errors.invalid_url_scheme") || "Invalid URL scheme. Only http/https are allowed.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid using inline fallback strings in translation calls. Remove the || default values and rely on the translation system's automatic fallback.
This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No new issues found in the latest changes. The security improvements are well-implemented.
Summary
This PR extends Claude Code support to work with alternative AI providers through environment variable overrides, specifically supporting Z.ai, Qwen (Alibaba Cloud), and DeepSeek.
Changes
Backend Enhancements
Configuration Reading: Added support for reading Claude Code configuration from multiple locations:
~/.claude/settings.json(global user settings)~/.claude/settings.local.json(local user settings)./.claude/settings.json(project-specific settings)./.claude/settings.local.json(project-specific local settings)~/.claude.json(main global config)Provider Detection: Automatically detects alternative providers based on
ANTHROPIC_BASE_URL:https://api.z.ai/v1https://dashscope.aliyuncs.com/api/v1https://api.deepseek.com/v1Dynamic Model Selection: Each provider has its own set of supported models that are dynamically loaded
Environment Variable Passing: Passes environment variables from Claude Code config to the subprocess
Frontend Updates
ApiOptions.tsxto display alternative provider models dynamicallyTesting
How to Test
Configure Claude Code to use an alternative provider:
~/.claude/settings.jsonwith:{ "env": { "ANTHROPIC_BASE_URL": "https://api.z.ai/v1", "ANTHROPIC_MODEL": "glm-4.5", "ANTHROPIC_API_KEY": "your-api-key" } }Open Roo Code and select Claude Code as the provider
The model dropdown should now show the alternative provider's models
Test that code generation works with the alternative provider
Related Issue
Fixes #8452
Checklist
Important
Extend Claude Code to support alternative providers (Z.ai, Qwen, DeepSeek) with dynamic model selection and frontend updates.
ClaudeCodeHandlernow supports alternative providers (Z.ai, Qwen, DeepSeek) viaANTHROPIC_BASE_URLdetection.settings.json,settings.local.json, etc.).run.ts.ApiOptions.tsxupdated to display models from alternative providers dynamically.claude-code-alternative-providers.spec.tsfor configuration reading, provider detection, and model fetching.claude-code.spec.tsto handle async initialization and model selection.webviewMessageHandler.ts.This description was created by
for 5a3a101. You can customize this summary. It will automatically update as commits are pushed.